/*
 * Description: performance pipe reciever
 */
#include "perf_pipe_reciever.h"
#include <algorithm>
#include <cstdlib>
#include <memory>
#include <thread>
#include "error.h"
#include "log.h"
#include "perf_lifecycle_handler.h"
#include "performance/perf_constants.h"
#include "performance/utils/perf_utils.h"
#include "performance/utils/registry_utils.h"
#include "preload/process_preload_entrance.h"
#include "common/guest_utils.h"

using namespace COMMON;

namespace PERF {

const std::string MESSAGE_SEP = "#";
constexpr const size_t SCENE_IDX = 0;
constexpr const size_t APP_PATH_IDX = 1;
constexpr const size_t APP_NAME_IDX = 2;
constexpr const size_t PID_IDX = 3;
constexpr const size_t APP_CMDLINE_IDX = 4;
constexpr const size_t APP_VECTOR_SIZE = 5;
constexpr const int32_t PID_NUMBER_BASE = 10;

int PerfPipeReciever::Init(std::shared_ptr<WINCONTROL::SendToHost> sendtoHost)
{
    PERF_LIFECYCLE_HANDLER.Init(sendtoHost);
    return ERROR_SUCCESS;
}

void PerfPipeReciever::CreatePipe(HANDLE &hPipe)
{
    if (hPipe != INVALID_HANDLE_VALUE) {
        return;
    }
    SECURITY_ATTRIBUTES sa;
    if (!GetSecurityAttributes(sa)) {
        LOG_ERR("[PERF - RecvMessage] - GetSrcurityAttributes failed!");
        return;
    }
    hPipe = CreateNamedPipe(PIPE_NAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                            PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, &sa);
    free(sa.lpSecurityDescriptor);
    sa.lpSecurityDescriptor = nullptr;
}

int PerfPipeReciever::RecvMessage()
{
    HANDLE hPipe = INVALID_HANDLE_VALUE;
    CreatePipe(hPipe);
    if (hPipe == INVALID_HANDLE_VALUE) {
        LOG_ERR("[PERF - RecvMessage] - CreateNamedPipe failed, errCode: {}", GetLastError());
        return DEFAULT_ERROR;
    }

    BOOL fConnected = ConnectNamedPipe(hPipe, nullptr) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
    if (!fConnected) {
        return DEFAULT_ERROR;
    }
    LOG_INFO("[PERF - RecvMessage] - Client connected, creating a processing thread. ");
    DWORD dwThreadId = 0;
    HANDLE hThread = INVALID_HANDLE_VALUE;
    auto th = std::thread(ProcessMessage, hPipe);
    th.detach();
    return OK;
}

BOOL PerfPipeReciever::GetSecurityAttributes(SECURITY_ATTRIBUTES &sa)
{
    sa.lpSecurityDescriptor = static_cast<PSECURITY_DESCRIPTOR>(malloc(SECURITY_DESCRIPTOR_MIN_LENGTH));
    if (sa.lpSecurityDescriptor == nullptr) {
        return FALSE;
    }
    InitializeSecurityDescriptor(sa.lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(sa.lpSecurityDescriptor, TRUE, nullptr, FALSE);
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = TRUE;
    return TRUE;
}

DWORD PerfPipeReciever::ProcessMessage(HANDLE hPipe)
{
    if (hPipe == INVALID_HANDLE_VALUE) {
        LOG_ERR("[PERF - ProcessMessage] - hPipe is invalid!");
        return DEFAULT_ERROR;
    }
    TCHAR *pchRequest = static_cast<TCHAR *>(malloc(BUFSIZE * sizeof TCHAR));
    if (pchRequest == nullptr) {
        LOG_ERR("[PERF - ProcessMessage] - pchRequest malloc failed!");
        return DEFAULT_ERROR;
    }
    TCHAR *pchReply = static_cast<TCHAR *>(malloc(BUFSIZE * sizeof TCHAR));
    if (pchReply == nullptr) {
        LOG_ERR("[PERF - ProcessMessage] - pchReply malloc failed!");
        free(pchRequest);
        return DEFAULT_ERROR;
    }
    DWORD dwBytesRead = 0;
    DWORD dwBytesReply = 0;
    DWORD dwWritten = 0;
    BOOL fSuccess = FALSE;
    LOG_INFO("[PERF - ProcessMessage] - thread created, receiving and processing messages.");
    do {
        fSuccess = ReadFile(hPipe, pchRequest, BUFSIZE * sizeof TCHAR, &dwBytesRead, nullptr);
        if (!fSuccess || dwBytesRead == 0) {
            if (GetLastError() == ERROR_BROKEN_PIPE) {
                LOG_ERR("[PERF - ProcessMessage] - client disconnected.");
            } else {
                LOG_ERR("[PERF - ProcessMessage] - ReadFile Failed, errCode: {}", GetLastError());
            }
            break;
        }
        std::string requestStr = pchRequest;
        if (!HandleMessage(requestStr)) {
            LOG_ERR("[PERF - ProcessMessage] - error occurred when handling message. errCode: {}", GetLastError());
        }
    } while (0);
    DisconnectNamedPipe(hPipe);
    SAFE_CLOSE_HANDLE(hPipe, INVALID_HANDLE_VALUE);
    free(pchRequest);
    free(pchReply);
    LOG_DEBUG("[PERF - ProcessMessage] - exiting.");
    return OK;
}

BOOL PerfPipeReciever::HandleMessage(const std::string &message)
{
    std::shared_ptr<PerfInfo> perfInfo = std::make_shared<PerfInfo>();
    if (perfInfo == nullptr) {
        LOG_ERR("[PERF - HandleMessage] - allocate perfInfo failed!");
        return FALSE;
    }
    if (!ParseMessage(message, perfInfo)) {
        LOG_ERR("[PERF - HandleMessage] - parse perfInfo failed!");
        return FALSE;
    }
    std::transform(perfInfo->appName.begin(), perfInfo->appName.end(), perfInfo->appName.begin(), ::tolower);
    auto pair = PERF_SECNE_TO_ENUM.find(perfInfo->sceneStr);
    if (pair == PERF_SECNE_TO_ENUM.end()) {
        LOG_ERR("[PERF - HandleMessage] - invalid perf scene!");
        return FALSE;
    }
    perfInfo->scene = pair->second;
    if (PRELOAD_OFFICE_APPS.find(perfInfo->appName) != PRELOAD_OFFICE_APPS.end()) {
        if (perfInfo->processId == (DWORD)-1) {
            PERF_UTILS.GetFilepathFromCmdLine(perfInfo->cmdLine, perfInfo->filepath);
            LOG_INFO("[PERF - HandleMessage] - awaken {}, filepath: {}", perfInfo->appName, perfInfo->filepath);
            PROCESS_PRELOAD_ENTRANCE.DoAwaken(perfInfo);
            perfInfo->processId = PERF_UTILS.GetProcessIdByName(perfInfo->appName.c_str());
        }
    }
    LOG_INFO("[PERF - HandleMessage] - starting perf lifecycle");
    PERF_LIFECYCLE_HANDLER.Start(perfInfo);
    return TRUE;
}

BOOL PerfPipeReciever::ParseMessage(const std::string &message, std::shared_ptr<PerfInfo> perfInfo)
{
    if (perfInfo == nullptr) {
        LOG_ERR("[PERF - ParseMessage] - perfInfo is null!");
        return FALSE;
    }
    std::vector<std::string> fragments = PERF_UTILS.Split(message, MESSAGE_SEP);
    if (fragments.size() != APP_VECTOR_SIZE) {
        LOG_ERR("[PERF - ParseMessage] - invalid fragments!");
        return FALSE;
    }
    perfInfo->sceneStr = fragments[SCENE_IDX];
    perfInfo->appPath = fragments[APP_PATH_IDX];
    perfInfo->appName = fragments[APP_NAME_IDX];
    perfInfo->processId = std::strtoul(fragments[PID_IDX].c_str(), nullptr, PID_NUMBER_BASE);
    perfInfo->cmdLine = fragments[APP_CMDLINE_IDX];
    return TRUE;
}
}  // namespace PERF